from PyQt6.QtWidgets import QWidget, QApplication
from PyQt6.QtGui import QPixmap, QPainter, QFontDatabase, QColor, QPalette, QPen, QFont, QFontInfo, QFontMetrics
from PyQt6.QtCore import Qt, pyqtSignal, QRecursiveMutex, QMutex, QTimer
from PyQt6 import QtGui, QtWidgets, QtCore
import pyte
from pyte import modes
from enum import Enum
import sys
import typing
import logging

from termqt import TerminalIO
from pyqt_te import QtScreen, QtScreenConfig

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter(
    "[%(asctime)s] > "
    "[%(filename)s:%(lineno)d] %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)


class ControlChar(Enum):
    NUL = 0  # Ctrl-@, null
    SOH = 1  # Ctrl-A, start of heading
    STX = 2  # Ctrl-B, start of text, bash shortcut for left arrow key
    ETX = 3  # Ctrl-C, end of text
    EOT = 4  # Ctrl-D, end of transmit, bash shortcut for delete
    ENQ = 5  # Ctrl-E, enquiry, bash shortcut for end of line
    ACK = 6  # Ctrl-F, acknowledge, bash shortcut for right arrow key
    BEL = 7  # Ctrl-G, bell, bash shortcut for leave history search
    BS = 8  # Ctrl-H, backspace
    TAB = 9  # Ctrl-I, tab
    LF = 10  # Ctrl-J, NL line feed, new line
    VT = 11  # Ctrl-K, vertical tab, bash shortcut for cut after cursor
    FF = 12  # Ctrl-L, from feed, bash shortcut for clear
    CR = 13  # Ctrl-M, carriage return
    SO = 14  # Ctrl-N, shift out, bash shortcut for go down in history
    SI = 15  # Ctrl-O, shift in, bash shortcut for run command
    DLE = 16  # Ctrl-P, data line escape, bash shortcut for go up in history
    DC1 = 17  # Ctrl-Q, device control 1, bash shortcut for resume output
    DC2 = 18  # Ctrl-R, device control 2, bash shortcut for search history
    DC3 = 19  # Ctrl-S, device control 3, bash shortcut for suspend output
    DC4 = 20  # Ctrl-T, device control 4, bash shortcut for swap character
    NAK = 21  # Ctrl-U, negative acknowledge, bash shortcut for cut before cursor
    SYN = 22  # Ctrl-V
    ETB = 23  # Ctrl-W, end of xmit block, bash shortcut for cut the word before cursor
    CAN = 24  # Ctrl-X, cancel
    EM = 25,  # Ctrl-Y, end of medium, bash shortcut for paste
    SUB = 26  # Ctrl-Z, substitute
    ESC = 27  # Ctrl-[, escape


class CursorState(Enum):
    ON = 1
    OFF = 2
    UNFOCUSED = 3


class Terminal(QWidget):
    need_draw_dirty = pyqtSignal()

    def __init__(self, parent=None):
        super(Terminal, self).__init__(parent)
        self._painter_lock = QtCore.QRecursiveMutex()
        config = QtScreenConfig()
        self._screen = QtScreen(config.min_terminal_width, config.min_terminal_height,
                                config=config, history=500)
        self._screen.set_mode(modes.DECAWM)
        self._terminal_io: TerminalIO = None
        self.stream = pyte.Stream(self._screen)

        self.setAttribute(Qt.WidgetAttribute.WA_InputMethodEnabled, True)

        self.need_draw_dirty.connect(self._repaint_handle)

    @property
    def terminal_screen(self) -> QtScreen:
        return self._screen

    def _feed(self, data):
        pass
        self.stream.feed(data.decode())
        self.need_draw_dirty.emit()

    def connect_io(self, io):
        io.stdout_callback = self._feed
        self._terminal_io = io

    def _repaint_handle(self):
        dirty = self._screen.dirty
        # print(dirty)

        if len(dirty) > 0:
            self._screen.draw_dirty()
            self.repaint()
        else:
            self.repaint()

    def _after_resize(self):
        # row = self.screen_row_count()
        # column = self.screen_column_count()
        # if self.terminal_row != row or self.terminal_col != column:
        #     self.terminal_row = row
        #     self.terminal_col = column
        #     print('resize', row, column)
        #     self._screen.resize(self.screen_row_count(), self.screen_column_count())
        #
        #     self._new_canvas()
        #
        #     self._paint_screen()
        #     if self._terminal_io:
        #         self._terminal_io.resize(row, column)
        pass

    def resize(self, *args) -> None:
        QWidget.resize(self, *args)

    def resizeEvent(self, e: typing.Optional[QtGui.QResizeEvent]) -> None:
        # super(Terminal, self).resizeEvent(e)

        self._after_resize()

    def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]) -> None:
        self._painter_lock.lock()
        _qp = QPainter(self)

        _qp.fillRect(self.rect(), self._screen.background)
        canvas = self._screen.canvas

        # _qp.setRenderHint(QPainter.Antialiasing)

        width = min(self.width(), canvas.width())
        height = min(self.height(), canvas.height())

        # self._screen.paint_lock.lock()
        _qp.drawPixmap(
            0, 0,
            canvas,
            0, 0, width, height
        )
        # self._screen.paint_lock.unlock()
        cursor_color = self._screen.cursor_color
        rect = self._screen.cursor_rect(2)

        _qp.fillRect(rect, cursor_color)

        # _qp.end()
        QWidget.paintEvent(self, e)
        self._painter_lock.unlock()

    def input(self, char):
        if self._terminal_io:
            if isinstance(char, bytes):
                self._terminal_io.write(char)
            elif isinstance(char, int):
                self._terminal_io.write(bytes([char]))

    def keyPressEvent(self, event: typing.Optional[QtGui.QKeyEvent]) -> None:
        key = event.key()
        modifiers = event.modifiers()
        text = event.text()

        while True:
            # This is a one-shot loop, because I want to use 'break'
            # to jump out of this block
            if key == Qt.Key.Key_Up:
                self.input(b'\x1b[A')
            elif key == Qt.Key.Key_Down:
                self.input(b'\x1b[B')
            elif key == Qt.Key.Key_Right:
                self.input(b'\x1b[C')
            elif key == Qt.Key.Key_Left:
                self.input(b'\x1b[D')
            else:
                break  # avoid the execution of 'return'
            return

        if not modifiers:
            while True:
                # This is a one-shot loop, because I want to use 'break'
                # to jump out of this block
                if key == Qt.Key.Key_Enter or key == Qt.Key.Key_Return:
                    self.input(ControlChar.CR.value)
                elif key == Qt.Key.Key_Delete or key == Qt.Key.Key_Backspace:
                    self.input(ControlChar.BS.value)
                elif key == Qt.Key.Key_Escape:
                    self.input(ControlChar.ESC.value)
                else:
                    break  # avoid the execution of 'return'
                return
        elif modifiers == Qt.KeyboardModifier.ControlModifier or modifiers == Qt.KeyboardModifier.MetaModifier:
            if key == Qt.Key.Key_A:
                self.input(ControlChar.SOH.value)
            elif key == Qt.Key.Key_B:
                self.input(ControlChar.STX.value)
            elif key == Qt.Key.Key_C:
                self.input(ControlChar.ETX.value)
            elif key == Qt.Key.Key_D:
                self.input(ControlChar.EOT.value)
            elif key == Qt.Key.Key_E:
                self.input(ControlChar.ENQ.value)
            elif key == Qt.Key.Key_F:
                self.input(ControlChar.ACK.value)
            elif key == Qt.Key.Key_G:
                self.input(ControlChar.BEL.value)
            elif key == Qt.Key.Key_H:
                self.input(ControlChar.BS.value)
            elif key == Qt.Key.Key_I:
                self.input(ControlChar.TAB.value)
            elif key == Qt.Key.Key_J:
                self.input(ControlChar.LF.value)
            elif key == Qt.Key.Key_K:
                self.input(ControlChar.VT.value)
            elif key == Qt.Key.Key_L:
                self.input(ControlChar.FF.value)
            elif key == Qt.Key.Key_M:
                self.input(ControlChar.CR.value)
            elif key == Qt.Key.Key_N:
                self.input(ControlChar.SO.value)
            elif key == Qt.Key.Key_O:
                self.input(ControlChar.SI.value)
            elif key == Qt.Key.Key_P:
                self.input(ControlChar.DLE.value)
            elif key == Qt.Key.Key_Q:
                self.input(ControlChar.DC1.value)
            elif key == Qt.Key.Key_R:
                self.input(ControlChar.DC2.value)
            elif key == Qt.Key.Key_S:
                self.input(ControlChar.DC3.value)
            elif key == Qt.Key.Key_T:
                self.input(ControlChar.DC4.value)
            elif key == Qt.Key.Key_U:
                self.input(ControlChar.NAK.value)
            elif key == Qt.Key.Key_V:
                self.input(ControlChar.SYN.value)
            elif key == Qt.Key.Key_W:
                self.input(ControlChar.ETB.value)
            elif key == Qt.Key.Key_X:
                self.input(ControlChar.CAN.value)
            elif key == Qt.Key.Key_Y:
                self.input(ControlChar.EM.value)
            elif key == Qt.Key.Key_Z:
                self.input(ControlChar.SUB.value)
            elif key == Qt.Key.Key_BracketLeft:
                self.input(ControlChar.ESC.value)
            return

        if text:
            self.input(text.encode('utf-8'))

        QWidget.keyPressEvent(self, event)

    def inputMethodEvent(self, event: typing.Optional[QtGui.QInputMethodEvent]) -> None:
        s = event.commitString()
        self.input(s.encode())


def main(argv):
    app = QApplication(argv)

    terminal = Terminal()
    terminal.resize(800, 600)
    terminal.show()

    bin = "cmd"

    screen = terminal.terminal_screen

    from termqt import TerminalWinptyIO
    from termqt import TerminalSshIO

    trans, channel = TerminalSshIO.connect_to_ssh("zarra_nas", "zarra", "ETPacs2008")
    terminal_io = TerminalSshIO(

       screen.columns, screen.lines,
        channel,
        logger=logger
    )

    # terminal_io = TerminalWinptyIO(
    #
    #      screen.lines, screen.columns,
    #     bin,
    #     logger=logger
    # )

    terminal.connect_io(terminal_io)

    terminal_io.spawn()

    sys.exit(app.exec())


if __name__ == '__main__':
    main(sys.argv)
